出戻りC++プログラマなので、極初歩的な情報を確認しています。
間違い等あればご教示下さい。
構造体などのアラインメントは、16bit用ソースを32bitで使う場合に問題になる場合があります。おそらく、同様に32bit→64bitのケースでも問題になるケースがあると思われるので、今からメモっておく価値がある?
サンプルソースはVisual Studio.NET 2003のWin32コンソールアプリケーション用で、プリコンパイルヘッダー関係は割愛しています。解説はWindowsの32bit環境であることを前提としています。
#pragma packの使用例 §
ソースコードの途中でアラインメントを変更します。
それだけでなく、pushとpopの機能を使うと、一時的に変更したものを戻すことができます。
コマンドラインオプションでも変更できますが、それによってソースコードの途中で切り替えることはできません。
サンプルソースを以下に示します。
アラインメントを変更した構造体と、変更しない構造体のサイズを比較します。
#include "stdafx.h"
typedef struct s1
{
char ch1;
int i1;
} t1;
#pragma pack(push,1)
typedef struct s2
{
char ch1;
int i1;
} t2;
#pragma pack(pop)
int _tmain(int argc, _TCHAR* argv[])
{
printf("size of t1=%d\n",sizeof(t1));
printf("size of t2=%d\n",sizeof(t2));
return 0;
}
実行結果は以下のようになります。
size of t1=8
size of t2=5
構造体t1では、アラインメントは4バイトであるため、1バイトしか占有しないch1の後には3バイトが詰められ、i1は4バイト目から始まります。つまり全体で8バイト占有します。
しかし、構造体t2では、#pragma pack(push,1)によって、アラインメントを1バイトに変更しているので、詰め物の3バイトは存在せず、サイズは5バイトになります。
そして、その後、#pragma pack(pop)によって、アラインメントを元の値(4バイト)に戻しています。
__declspec(align(#))の使用例 §
__declspec(align(#))は、あるデータが配置される位置のアラインメントを指定します。たとえば、32bit境界に合わせた位置に置かねば正常に扱えないデータを記述する場合などに使用します。(x86では馴染みが薄いかもしれないが、X68000プログラミング経験者なら、良く分かる?)
サンプルソースを以下に示します。
アラインメントを指定した構造体を複数含む構造体と、そうではない構造体を複数含む構造体のサイズを比較します。
#include "stdafx.h"
typedef struct
{
char ch1;
} a1;
typedef __declspec(align(4)) struct
{
char ch1;
} a2;
typedef struct
{
a1 dummy1;
a1 dummy2;
} s1;
typedef struct
{
a2 dummy1;
a2 dummy2;
} s2;
int _tmain(int argc, _TCHAR* argv[])
{
printf("size of s1=%d\n",sizeof(s1));
printf("size of s2=%d\n",sizeof(s2));
return 0;
}
実行結果は以下のようになります。
size of s1=2
size of s2=8
構造体s1とs2の定義は、#pragma packのような特殊な記述も含まず、どちらも同じように見えます。しかし、内部で使用された型(a1とa2)の定義の相違から、全体としてのサイズは一致しなくなります。つまり、charのアラインメントは1であるため、char2個を含む構造体のサイズは2バイトになりますが、__declspec(align(4))によって強制的にアラインメントを4バイトに設定された構造体は、4バイト境界に配置されるために、8バイトのサイズを占有します。
__alignof演算子の使用例 §
指定した型のアラインメントのバイト数を取得する演算子です。
テスト実行環境では、charなら1、intは4になります。
サンプルソースを以下に示します。
同じ型の値を2つ並べ、そのアドレスの差を取ってアラインメントの値を調べ、それが__alignof演算子の値と一致することを確認しています。
(なお、このような一致は、サイズがアラインメントを上回る型では成立しないことに注意が必要)
#include "stdafx.h"
#include "assert.h"
typedef struct
{
char c1;
char c2;
} s1;
typedef struct
{
short int c1;
short int c2;
} s2;
int _tmain(int argc, _TCHAR* argv[])
{
s1 t1;
assert( __alignof(char) == (char*)&t1.c2 - (char*)&t1.c1 );
s2 t2;
assert( __alignof(short int) == (char*)&t2.c2 - (char*)&t2.c1 );
return 0;
}
このプログラムは、実行に成功した場合、何も表示しません。
(char*)&t1.c2 - (char*)&t1.c1は、連続して記述されたchar型の変数のアドレスの差分を計算しています。この値は、char型のアラインメントつまり__alignof(char)の値に一致しているはずです。
同様に、(char*)&t2.c2 - (char*)&t2.c1は、short int型で同じような計算を行っています。アドレスの差分を得るために、(char*)というキャストを付けていることに注意して下さい。これを付けないと、意図した値が取得できません。
この知識が必要とされる状況 §
このような知識は、たとえば以下のように記述された16bit環境用のコードを32bit環境に移行させる際に必須となります。
typedef struct {
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
RGBQUAD aColors[1];
} DIB_FILE_IMAGE, FAR * LP_DIB_FILE_IMAGE;
この構造体は、BITMAPFILEHEADERが4バイト境界で終わらないために、2バイトの詰め物が詰められ、その結果としてbmihは正しいオフセット値を持たなくなります。
これを解決するには、手前に#pragma pack(push,2)を、後に#pragma pack(pop)を付ければOKです。
まとまらないまとめ §
以上、完全にまとめ切れていませんが、最低限のことだけまとめておきました。
Windows上のC++の世界は、今、32bitと64bitの狭間で流動しており、非常に興味深い状況です。これだけ強力なアラインメント制御機能が揃っていることは、64bit化で役立つツールとして準備された側面がおそらくあるのでしょう。